Windows下使用标准Shell接口遍历文件和文件夹(2) 转自我的CSDN | 您所在的位置:网站首页 › shell 遍历文件夹 › Windows下使用标准Shell接口遍历文件和文件夹(2) 转自我的CSDN |
TreeView实现目录树-tree_view的相关方法
使用TreeView实现目录树,其中每个节点都是一个文件夹对象(也可能是虚拟文件夹)。而ListView显示所有的文件对象和子文件夹对象。如下图:
如果某个文件夹对象拥有子文件夹,这个文件夹所对应的节点前面会有一个表示可以展开的符号“+”。点击“+”可以展开当前节点,显示这个文件夹对象中的所有子文件夹对象。 实现的时候,我们的TreeView包含一个普通的子窗体和一个TreeView窗体,将这两个窗体作为TreeView使用。TreeView窗体是普通子窗体的子窗体。在使用的时候,如果在主窗体使用我们提供的方法创建TreeView,实际上是在主窗体上创建了一个子窗体,在子窗体中创建一个TreeView。下面要实现的是一个普通的TreeView所必备的一些功能的封装。我称为tree_view。要实现真正的显示本地的命名空间树,或者显示远程主机的命名空间树的时候,可以扩展使用tree_view来实现。 tree_view的实现下面先定义了一些要使用的回调函数: 1: typedef HRESULT(*pfn_tv_after_create)(struct tree_view* tv); 2: typedef BOOL(*pfn_tv_first_expanding)(struct tree_view* tv, LPTVITEM ptvi); 3: typedef BOOL(*pfn_tv_expanding)(struct tree_view* tv, LPTVITEM ptvi); 4: typedef BOOL(*pfn_tv_collapse)(struct tree_view* tv, LPTVITEM ptvi); 5: typedef HRESULT(*pfn_tv_delitem)(struct tree_view* tv, LPTVITEM ptvi); 6: typedef HRESULT(*pfn_tv_selchanged)(struct tree_view* tv, LPTVITEM ptvi_old, LPTVITEM ptvi_new); 7: typedef HRESULT(*pfn_tv_check_drag_type)(struct tree_view* tv, CLIPFORMAT cfFormat); 8: typedef HRESULT(*pfn_tv_drop)(struct tree_view* tv, CLIPFORMAT cfFormat, STGMEDIUM medium); 9: typedef HRESULT(*pfn_tv_begin_drag) (struct tree_view* tv, LPNMTREEVIEW lpNMTV);这些回调函数实际上是为了扩展准备的,供给tree_view的使用者实现一些特殊功能所使用的。 下面的结构保存所有的回调函数指针: 1: 5: struct tv_callback 6: { 7: pfn_tv_after_create fn_after_create; 8: pfn_tv_first_expanding fn_first_expanding; 9: pfn_tv_expanding fn_expanding; 10: pfn_tv_collapse fn_collapse; 11: pfn_tv_delitem fn_delitem; 12: pfn_tv_selchanged fn_selchanged; 13: pfn_tv_check_drag_type fn_check_drag_type; 14: pfn_tv_drop fn_drop; 15: pfn_tv_begin_drag fn_begin_drag; 16: };下面的结构保持着tree_view所需要的所有信息: 1: 5: struct tree_view 6: { 7: struct tv_callback cb; 8: HWND hParent; 9: HWND hWnd; 10: HWND hTree; 11: ATOM atom; 12: BOOL fDragging; 13: LPVOID lpArgs; 14: };下面这个结构是tree_view中每个节点需要保存的信息: 1: 5: struct tv_node_info 6: { 7: LPTSTR lpszText; 8: int iImage; 9: int iSelImage; 10: int iHasChild; 11: HTREEITEM hParent; 12: HTREEITEM hAfter; 13: LPVOID lpParam; 14: };下面是提供的对外方法: 1: 12: struct tree_view* tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos, 13: struct tv_callback cb, LPVOID lpArgs); 14: 15: 19: void tv_free (struct tree_view* tv); 20: 21: 28: LRESULT CALLBACK tv_WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 29: 30: 35: HWND tv_get_tree_view (struct tree_view* tv); 36: 37: 43: HTREEITEM tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni); 44: 45: 53: HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs);下面逐步介绍各个方法 struct tree_view* tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos, struct tv_callback cb, LPVOID lpArgs);这个方法创建一个tree_view对象指针,这个对象指针不再使用的时候需要调用tv_free来释放。 1: struct tree_view* 2: tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos, 3: struct tv_callback cb, LPVOID lpArgs) 4: { 5: struct tree_view* tv = NULL; 6: HINSTANCE hInst = NULL; 7: WNDCLASSEX wcex; 8: HWND hWnd; 9: 10: tv = (struct tree_view*) malloc (sizeof (struct tree_view)); 11: if (NULL == tv) 12: { 13: return NULL; 14: } 15: ZeroMemory (tv, sizeof (struct tree_view)); 16: 17: hInst = (HINSTANCE) GetWindowLong (hParent, GWL_HINSTANCE); 18: ZeroMemory (&wcex, sizeof (WNDCLASSEX)); 19: wcex.cbSize = sizeof(WNDCLASSEX); 20: 21: wcex.style = CS_HREDRAW | CS_VREDRAW; 22: wcex.lpfnWndProc = tv_WndProc; 23: wcex.cbClsExtra = 0; 24: wcex.cbWndExtra = 0; 25: wcex.hInstance = hInst; 26: wcex.hIcon = NULL; 27: wcex.hCursor = NULL; 28: wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 29: wcex.lpszMenuName = NULL; 30: wcex.lpszClassName = lpszClassName; 31: wcex.hIconSm = NULL; 32: 33: tv->atom = RegisterClassEx(&wcex); 34: if (!tv->atom) 35: { 36: free (tv); 37: return NULL; 38: } 39: 40: tv->hParent = hParent; 41: memcpy (&(tv->cb), &cb, sizeof (struct tv_callback)); 42: tv->lpArgs = lpArgs; 43: 44: hWnd = CreateWindowEx (WS_EX_CLIENTEDGE, tv->atom, lpszTitle, WS_CHILD | WS_VISIBLE, 45: rtPos.left, rtPos.top, rtPos.right-rtPos.left, rtPos.bottom-rtPos.top, hParent, NULL, hInst, tv); 46: 47: if (!hWnd) 48: { 49: DWORD dwErr = GetLastError (); 50: free (tv); 51: return NULL; 52: } 53: 54: ShowWindow (hWnd, SW_SHOW); 55: UpdateWindow (hWnd); 56: 57: return tv; 58: }这个函数创建了tree_view结构对象并注册一个窗体类,最后调用CreateWindowEx创建一个窗体,这个窗体就是TreeView的父窗体,我们会在这个窗体的WM_CREATE消息中创建TreeView。用户传递的参数pArgs保持在tree_view的pArgs成员中,这是用户数据。特别注意的是在调用CreateWindowEx时,最后一个参数传递了tree_view的指针,这个指针在tree_view的父窗体(以后称为当前窗体)接收到WM_CREATE消息时放在CREATESTRUCT结构的lParam成员中。 void tv_free (struct tree_view* tv);这个方法销毁创建的当前窗体和tree_view对象。 1: void 2: tv_free (struct tree_view* tv) 3: { 4: HINSTANCE hInst = NULL; 5: if (tv) 6: { 7: if (tv->hWnd) 8: { 9: DestroyWindow (tv->hWnd); 10: } 11: if (tv->atom) 12: { 13: hInst = (HINSTANCE) GetWindowLong (tv->hParent, GWL_HINSTANCE); 14: UnregisterClass ((LPCTSTR)tv->atom, hInst); 15: } 16: free (tv); 17: } 18: }下面是当前窗体的消息响应函数,我们先给出代码,然后进行详细介绍: 1: LRESULT CALLBACK 2: tv_WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 3: { 4: switch (message) 5: { 6: case WM_CREATE: 7: { 8: CREATESTRUCT* cs; 9: cs = (CREATESTRUCT*) lParam; 10: return _tv_on_create (hWnd, cs); 11: } 12: break; 13: case WM_NOTIFY: 14: { 15: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 16: if (NULL == tv) 17: { 18: return TRUE; // 有错误,不能展开 19: } 20: switch (((LPNMHDR) lParam)->code) 21: { 22: case TVN_ITEMEXPANDING: 23: { 24: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam; 25: switch (lpNMTV->action) 26: { 27: case TVE_EXPAND: 28: { 29: if (!(lpNMTV->itemNew.state & TVIS_EXPANDEDONCE)) 30: { 31: return tv->cb.fn_first_expanding (tv, &(lpNMTV->itemNew)); 32: } 33: else 34: { 35: return tv->cb.fn_expanding (tv, &(lpNMTV->itemNew)); 36: } 37: } 38: break; 39: case TVE_COLLAPSE: 40: { 41: return tv->cb.fn_collapse (tv, &(lpNMTV->itemNew)); 42: } 43: break; 44: } 45: } 46: break; 47: case TVN_DELETEITEM: 48: { 49: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam; 50: return tv->cb.fn_delitem (tv, &(lpNMTV->itemOld)); 51: } 52: break; 53: case TVN_SELCHANGED: 54: { 55: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam; 56: return tv->cb.fn_selchanged (tv, &(lpNMTV->itemOld), &(lpNMTV->itemNew)); 57: } 58: break; 59: case TVN_BEGINDRAG: 60: { 61: LPNMTREEVIEW* lpNMTV = (LPNMTREEVIEW) lParam; 62: return _tv_on_begin_drag (tv, lpNMTV); 63: } 64: break; 65: } 66: } 67: break; 68: case WM_MOUSEMOVE: 69: { 70: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 71: return _tv_on_mouse_move_drag (tv, wParam, lParam); 72: } 73: break; 74: case WM_LBUTTONUP: 75: { 76: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 77: return _tv_on_lbtn_up_drag (tv, wParam, lParam); 78: } 79: break; 80: case TV_M_CHANGEFOCUS: 81: { 82: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA); 83: TreeView_SelectDropTarget(tv->hTree, NULL); 84: TreeView_SelectItem(tv->hTree, (HTREEITEM)lParam) ; 85: } 86: break; 87: default: 88: return DefWindowProc(hWnd, message, wParam, lParam); 89: } 90: return S_OK; 91: } WM_CREATE 消息当调用CreateWindowEx创建当前窗体时,我们在最后的参数中传入了tree_view对象的指针。这里通过CREATESTRUCT结构的lParam成员获得了这个指针,并调用了我们自己的内部函数_tv_on_create: 1: static LRESULT 2: _tv_on_create (HWND hWnd, CREATESTRUCT* cs) 3: { 4: HRESULT hr; 5: RECT rt; 6: HWND hTree = NULL; 7: struct tree_view* tv = NULL; 8: IDropTarget* pDT = NULL; 9: struct idt_lpVtbl idtVtbl; 10: 11: GetClientRect (hWnd, &rt); 12: 13: // 创建TreeView 14: hTree = CreateWindow (WC_TREEVIEW, 15: "", 16: WS_CHILD | LVS_REPORT | WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT|TVS_SHOWSELALWAYS , 17: 0, 18: 0, 19: rt.right-rt.left, 20: rt.bottom-rt.top, 21: hWnd, 22: (HMENU) ID_LOCAL_DIR_TREE, 23: (HINSTANCE) GetWindowLong (hWnd, GWL_HINSTANCE), 24: NULL); 25: if (NULL == hTree) 26: { 27: return -1; 28: } 29: 30: tv = (struct tree_view*) cs->lpCreateParams; 31: tv->hTree = hTree; 32: tv->hWnd = hWnd; 33: 34: // 设置窗体参数 35: SetWindowLong (hWnd, GWL_USERDATA, (LONG) tv); 36: 37: 51: if (tv->cb.fn_after_create) 52: { 53: return tv->cb.fn_after_create (tv); 54: } 55: return S_OK; 56: }与拖拽有关的方法我都注释掉了,因为在测试拖拽的时候遇到了一些问题。这个方法调用CreateWindow创建了一个TreeView子窗体,并将tree_view对象指针保存在窗体的用户数据中,以便以后处理其它消息的时候使用。最后我们调用了用户的回调函数。 下面我们看一下WM_NOTIFY消息,这个消息用于当前窗体处理TreeView的事件。我们处理了TVN_ITEMEXPANDING、TVN_DELETEITEM、TVN_SELCHANGED和TVN_BEGINDRAG事件。 TVN_ITEMEXPANDING 事件当TreeView中的某个节点被展开或者收缩的时候,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的action成员表示当前节点是被展(TVE_EXPAND)开还是被收缩(TVE_COLLAPSE)。NMTREEVIEW结构中的itemNew表示发出事件的当前节点。如果是被展开,当前节点的state成员标识着当前节点的状态,当某个节点第一次被展开时,state的TVIS_EXPANDEDONCE标志没有被置位,第一次展开完成后,state的TVIS_EXPANDEDONCE标志被置位。所以我们通过探测TVIS_EXPANDEDONCE标志是否被置位,来判断当前节点是否是第一次被展开。通常在某个节点第一次被展开时,准备相关的数据(如创建并填充所有的子节点);而不是第一次被展开时,我们可以什么都不做。无论是被展开还是被收缩,具体的操作我们都是调用回调函数,让用户实现。 TVN_DELETEITEM 事件当TreeView中某个节点被删除时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的itemOld成员表示要被删除的节点。在这里我们调用用户参数,因为节点是用户创建的,用户可能在节点中存储了用户自定义信息,在这里用户可以释放用户自定义信息所使用的资源。 TVN_SELCHANGED 事件当TreeView中某个节点被选中时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。其中itemOld表示以前被选中的节点,itemNew表示后来被选中的新节点。这里调用用户的回调函数,让用户处理,如控制ListView显示当前选中的新节点的文件对象和子文件夹对象等。 TVN_BEGINDRAG 事件当TreeView中某个节点开始被拖拽时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的itemNew表示被拖拽的当前节点。这里调用_tv_on_begin_drag函数: 1: static LRESULT 2: _tv_on_begin_drag (struct tree_view* tv, LPNMTREEVIEW lpNMTV) 3: { 4: 28: return S_OK ; 29: }这个函数被注释的部分先调用了用户的回调函数,然后设置拖拽的图标信息。 WM_MOUSEMOVE 消息这个消息中调用了_tv_on_mouse_move_drag来处理拖拽过程中的图标显示: 1: static BOOL 2: _tv_on_mouse_move_drag (struct tree_view* tv, WPARAM wParam, LPARAM lParam) 3: { 4: POINT pnt; 5: HTREEITEM hItem = NULL; 6: TVHITTESTINFO tv_ht; 7: 8: pnt.x = GET_X_LPARAM(lParam); 9: pnt.y = GET_Y_LPARAM(lParam); 10: 11: if (tv->fDragging) 12: { 13: //unlock window and allow updates to occur 14: ImageList_DragLeave(NULL) ; 15: ClientToScreen(tv->hWnd, &pnt) ; 16: //check with the tree control to see if we are on an item 17: ZeroMemory(&tv_ht, sizeof(TVHITTESTINFO)); 18: tv_ht.flags = TVHT_ONITEM; 19: tv_ht.pt.x = pnt.x; 20: tv_ht.pt.y = pnt.y; 21: ScreenToClient(tv->hTree, &(tv_ht.pt)); 22: hItem = (HTREEITEM)SendMessage(tv->hTree, TVM_HITTEST, 0, (LPARAM)&tv_ht); 23: 24: if (hItem) 25: { 26: //if we had a hit, then drop highlite the item 27: TreeView_SelectItem (tv->hTree, hItem); 28: } 29: 30: //paint the image in the new location 31: ImageList_DragMove(pnt.x,pnt.y); 32: //lock the screen again 33: ImageList_DragEnter(NULL, pnt.x, pnt.y); 34: } 35: return TRUE; 36: }在这个处理过程中,不断的探测鼠标拖拽的对象覆盖了哪个节点,使被覆盖的节点处于被选中状态。 WM_LBUTTONUP 消息这个消息表示拖拽结束,调用_tv_on_lbtn_up_drag停止拖拽图标的显示: 1: static BOOL 2: _tv_on_lbtn_up_drag (struct tree_view* tv, WPARAM wParam, LPARAM lParam) 3: { 4: HTREEITEM hItem = NULL; 5: TVHITTESTINFO tv_ht; 6: TVITEM tvi; 7: ZeroMemory(&tvi, sizeof(TVITEM)); 8: ZeroMemory(&tv_ht, sizeof(TVHITTESTINFO)); 9: 10: if (tv->fDragging) 11: { 12: ImageList_DragLeave(NULL); 13: ImageList_EndDrag(); 14: ReleaseCapture(); 15: //determin if we let up on an item 16: GetCursorPos(&(tv_ht.pt)); 17: ScreenToClient(tv->hTree, &(tv_ht.pt)); 18: tv_ht.flags = TVHT_ONITEM; 19: hItem = (HTREEITEM)SendMessage(tv->hTree, TVM_HITTEST, 0, (LPARAM)&tv_ht); 20: ShowCursor(TRUE); 21: tv->fDragging = FALSE; 22: if (hItem) 23: { 24: 25: PostMessage(tv->hWnd, TV_M_CHANGEFOCUS, (WPARAM)0, (LPARAM)hItem); 26: } 27: } 28: return TRUE; 29: }在这个消息中调用PostMessage发送了一个自定义的TV_M_CHANGEFOCUS异步消息,在这个消息中,我们调用TreeView_SelectDropTarget来选中最终拖拽的目标节点。 HWND tv_get_tree_view (struct tree_view* tv);用于获得tree_view中的TreeView窗体句柄。 1: HWND 2: tv_get_tree_view (struct tree_view* tv) 3: { 4: if (tv) 5: { 6: return tv->hTree; 7: } 8: return NULL; 9: } HTREEITEM tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni);用于插入一个新节点: 1: HTREEITEM 2: tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni) 3: { 4: TVINSERTSTRUCT tvins; 5: TVITEM tvi; 6: if (NULL == tv || NULL == tvni) 7: { 8: return NULL; 9: } 10: 11: ZeroMemory (&tvins, sizeof (TVINSERTSTRUCT)); 12: ZeroMemory (&tvi, sizeof (TVITEM)); 13: 14: tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; 15: if (tvni->iHasChild) 16: { 17: tvi.mask |= TVIF_CHILDREN; 18: tvi.cChildren = 1; 19: } 20: tvi.cchTextMax = MAX_PATH; 21: tvi.pszText = tvni->lpszText; 22: tvi.iImage = tvni->iImage; 23: tvi.iSelectedImage = tvni->iSelImage; 24: tvi.lParam = (LPARAM) tvni->lpParam; 25: tvins.item = tvi; 26: tvins.hInsertAfter = tvni->hAfter; 27: tvins.hParent = tvni->hParent; 28: return TreeView_InsertItem (tv->hTree, &tvins); 29: }在这个函数中,调用TreeView_InsertIterm来插入节点,并且设置了节点的文字、图片、被选中时的图片以及用户参数。 HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs);这个函数用于拖拽某个节点后,构造拖拽信息,并调用DoDragDrop函数进行拖拽后的处理。其中需要实现IDataObject和IDropSource这两个系统接口。如果想实现将某个节点表示的文件夹拖拽到支持拖拽的目标后,将这个文件夹拷贝到目标对象,那么必须调用这个函数。它可以支持将节点拖拽到Explorer。以后再详细介绍吧: 1: HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs) 2: { 3: 4: IDataObject* pDO = NULL; 5: IDropSource* pDS = NULL; 6: struct ido_lpVtbl ido_vtbl; 7: struct ids_lpVtbl ids_vtbl; 8: DWORD dwOKEffect; 9: 10: ZeroMemory (&ido_vtbl, sizeof (struct ido_lpVtbl)); 11: ZeroMemory (&ids_vtbl, sizeof (struct ids_lpVtbl)); 12: 13: ido_vtbl.DAdvise = _tv_DAdvise; 14: ido_vtbl.DUnadvise = _tv_DUnadvise; 15: ido_vtbl.EnumDAdvise = _tv_EnumDAdvise; 16: ido_vtbl.EnumFormatEtc = _tv_EnumFormatEtc; 17: ido_vtbl.GetCanonicalFormatEtc = _tv_GetCanonicalFormatEtc; 18: ido_vtbl.GetData = _tv_GetData; 19: ido_vtbl.GetDataHere = _tv_GetDataHere; 20: ido_vtbl.QueryGetData = _tv_QueryGetData; 21: ido_vtbl.SetData = _tv_SetData; 22: 23: pDO = ido_create (&ido_vtbl, pFormatEtc, pStgMedium, nCount, pArgs); 24: if (NULL == pDO) 25: { 26: return S_FALSE; 27: } 28: 29: ids_vtbl.GiveFeedback = _tv_GiveFeedback; 30: ids_vtbl.QueryContinueDrag = _tv_QueryContinueDrag; 31: 32: pDS = ids_create (&ids_vtbl, pArgs); 33: if (NULL == pDS) 34: { 35: pDO->lpVtbl->Release (pDO); 36: return S_FALSE; 37: } 38: 39: ZeroMemory (&dwOKEffect, sizeof (DWORD)); 40: 41: return DoDragDrop (pDO, pDS, DROPEFFECT_COPY, &dwOKEffect); 42: } |
CopyRight 2018-2019 实验室设备网 版权所有 |